<?php

/**
 * 
 * @desc This class provides routs of getters/objects classes and tables 
 * @see http://code.google.com/p/dabase
 * @author Barbushin Sergey http://www.linkedin.com/in/barbushin
 * 
 */
class DaBase_Router {
	
	protected $tablesPrefix;
	protected $gettersClassesPrefix;
	protected $objectsClassesPrefix;
	protected $defaultGettersClass = self::rootGettersClass;
	protected $defaultObjectsClass = self::rootObjectsClass;
	protected $throwExceptionOnWrongSubclass = true;
	
	public $ruleClassByGetter = 'ucAllWords';
	public $ruleObjectByGetter = 'manyToOne|ucAllWords';
	public $ruleObjectByTable = 'manyToOne|ucAllWords';
	public $ruleGetterByTable = 'ucNotFirstWords';
	public $ruleTableByGetter = 'lcAllWords';
	public $ruleTableByObject = 'lcAllWords|oneToMany';
	public $ruleJoinFieldByTable = 'manyToOne|ucNotFirstWords|Id';
	
	const rootGettersClass = 'DaBase_Getter';
	const rootObjectsClass = 'DaBase_Object';

	public function __construct($gettersClassesPrefix = null, $objectsClassesPrefix = null, $defaultGettersClass = null, $defaultObjectsClass = null, $tablesPrefix = null) {
		if ($defaultGettersClass) {
			$this->defaultGettersClass = $defaultGettersClass;
		}
		if (!$this->isClassExists($this->defaultGettersClass, self::rootGettersClass)) {
			throw new DaBase_Exception('Default getters class "' . $this->defaultGettersClass . '" must be subclass of "' . self::rootGettersClass . '"');
		}
		if ($defaultObjectsClass) {
			$this->defaultObjectsClass = $defaultObjectsClass;
		}
		if (!$this->isClassExists($this->defaultObjectsClass, self::rootObjectsClass)) {
			throw new DaBase_Exception('Default objects class "' . $this->defaultObjectsClass . '" must be subclass of "' . self::rootObjectsClass . '"');
		}
		if ($gettersClassesPrefix) {
			$this->gettersClassesPrefix = $gettersClassesPrefix;
		}
		if ($objectsClassesPrefix) {
			$this->objectsClassesPrefix = $objectsClassesPrefix;
		}
		if ($tablesPrefix) {
			$this->tablesPrefix = $tablesPrefix;
		}
	}

	public function setTablesPrefix($prefix) {
		$this->tablesPrefix = $prefix;
	}

	protected function isClassExists($class, $isSubclassOf = null) {
		try {
			$classExists = class_exists($class);
		}
		catch (Exception $e) {
			$classExists = false;
		}
		if ($classExists) {
			if ($isSubclassOf && $class != $isSubclassOf && !is_subclass_of($class, $isSubclassOf)) {
				if ($this->throwExceptionOnWrongSubclass) {
					throw new DaBase_Exception('Class "' . $class . '" is not subclass of "' . $isSubclassOf . '"');
				}
			}
			else {
				return true;
			}
		}
		return false;
	}

	public function throwExceptionOnWrongSubclass($throw) {
		$this->throwExceptionOnWrongSubclass = $throw;
	}

	public function getGetterByName($getterName, DaBase_Db $db) {
		if ($getter = $this->getCachedGetter($getterName, $db)) {
			return $getter;
		}
		
		$getterClass = $this->getGetterClassByGetterName($getterName);
		$objectsClass = constant("$getterClass::objectsClass") ? constant("$getterClass::objectsClass") : $this->getObjectsClassByGetterName($getterName);
		
		if (constant("$objectsClass::table")) {
			$tableName = constant("$objectsClass::table");
		}
		elseif ($objectsClass != $this->defaultObjectsClass) {
			$tableName = $this->getTableNameByObjectsClass($objectsClass);
		}
		else {
			$tableName = $this->getTableNameByGetterName($getterName);
		}
		
		$getter = $this->getGetter($db, $tableName, $getterClass, $objectsClass, $getterName);
		
		$this->setCachedGetter($getterName, $getter, $db);
		return $getter;
	}
	
	public function getGetter(DaBase_Db $db, $tableName, $getterClass=null, $objectsClass=null, $getterName=null) {
		if(!$getterClass) {
			$getterClass = $this->defaultGettersClass;
		}
		if(!$objectsClass) {
			$objectsClass = $this->defaultObjectsClass;
		}
		return new $getterClass($db, $tableName, $objectsClass, $getterName);
	}

	/**************************************************************
	 ROUTS CACHE
	 **************************************************************/
	
	protected function setCachedGetter($getterName, DaBase_Getter $getter, DaBase_Db $db) {
		$this->cachedGetter[self::getCacheKey($getterName, $db)] = clone $getter;
	}

	protected static function getCacheKey($getterName, DaBase_Db $db) {
		return $getterName . spl_object_hash($db);
	}

	protected function getCachedGetter($getterName, DaBase_Db $db) {
		$key = self::getCacheKey($getterName, $db);
		return isset($this->cachedGetters[$key]) ? clone $this->cachedGetters[$key] : false;
	}

	/**************************************************************
	 ROUTS METHODS (GETTER -> OBJECT -> TABLE)
	 **************************************************************/
	
	public function getGetterClassByGetterName($getterName) {
		$getterClass = $this->gettersClassesPrefix . self::fetchNameByRule($getterName, $this->ruleClassByGetter);
		if ($this->isClassExists($getterClass, self::rootGettersClass)) {
			return $getterClass;
		}
		return $this->defaultGettersClass;
	}

	protected function getObjectsClassByGetterName($getterName) {
		$objectsClass = $this->objectsClassesPrefix . self::fetchNameByRule($getterName, $this->ruleObjectByGetter);
		if ($this->isClassExists($objectsClass, self::rootObjectsClass)) {
			return $objectsClass;
		}
		return $this->defaultObjectsClass;
	}

	public function getTableNameByGetterName($getterName) {
		return $this->tablesPrefix . self::fetchNameByRule($getterName, $this->ruleTableByGetter);
	}

	public function getTableNameByObjectsClass($objectsClass) {
		if ($objectsClass != $this->defaultObjectsClass) {
			$objectsClass = preg_replace('/^' . $this->objectsClassesPrefix . '/', '', $objectsClass);
		}
		return $this->tablesPrefix . self::fetchNameByRule($objectsClass, $this->ruleTableByObject);
	}

	public function getJoinFieldNameByGetter(DaBase_Getter $getter) {
		return self::fetchNameByRule($getter->getTable(), $this->ruleJoinFieldByTable);
	}

	/**************************************************************
	 FETCHING NAMES BY RULES  
	 **************************************************************/
	
	public static function fetchNameByRule($name, $rulesString) {
		foreach (explode('|', $rulesString) as $rule) {
			switch ($rule) {
				case 'manyToOne':
					$name = preg_replace('/(e?s)([_A-Z]|$)/', '\\2', $name); // usersComments -> userComment OR users_comments -> user_comment
					break;
				case 'oneToMany':
					$name = preg_replace('/ss([_A-Z]|$)/', 'ses\\1', preg_replace('/([A-Z_]|$)/', 's\\1', $name)); // userComment -> usersComments OR user_comment -> users_comments
					break;
				case 'ucAllWords':
					$name = preg_replace('/(^|_)(.)/e', 'strtoupper(\'\\2\')', $name); // userComment -> UserComment OR user_comment -> UserComment
					break;
				case 'ucNotFirstWords':
					$name = preg_replace('/_(.)/e', 'strtoupper(\'\\1\')', $name); // UserComment -> userComment OR user_comment -> userComment
					break;
				case 'lcAllWords':
					$name = strtolower(preg_replace('/(.)([A-Z])/', '\\1_\\2', $name)); // UserComment -> user_comment OR userComment -> user_comment
					break;
				default :
					$name .= $rule;
			}
		}
		return $name;
	}
}